package org.activequant.math.algorithms;

/**
 * Fitter that uses exponentially-weighted rolling regression to compute
 * model parameters. The model process is AR(1) (autoregressive moving average with p = q = 1):
 * <pre>
 *  P_{k + 1} - mu = Phi * (P_k - mu) + Sigma * epsilon_k 
 * </pre>
 * where P_k is price at k-th step. mu - mean value of price,
 * epsilon_k is uncorrelated "noise" term, Sigma - amplitude of the "noise".
 * <p>
 * Parameter Phi can be interpreted via correlation length Tau:
 * <pre>
 * Tau = -1 / ln(Phi)
 * </pre>
 * the meaning of Tau is the number of steps that needed for the trend to reverse. Note that AR(1) is mean-reverting 
 * process (no global trend).
 * <p>
 * Three parameters of AR(1) are computed via following values:
 * <ul>
 *    <li> mean value of P: mu = <<P_k>> (does not depend on k).
 *    <li> squared variance: <<(P_k - mu)^2>>
 *    <li> differential covariance with lag n: <<(P_{k+n} - P_k)^2>>
 * </ul> 
 * Note that any positive lag value may be used (since in AR1 model they are
 * related). However, very big lags show numerical problems (because they attempt to
 * measure exponentially decaying function at large distance, so that errors are amplified
 * exponentially).<br/>
 * A subclass of the EMAVarAccumulator. Holds the following associated variables:
 * <ul>
 * <li>memory(double[])</li>
 * <li>start(int)</li>
 * <li>covariance(EMAAccumulator)</li>
 * </ul>
 * Holds the following inherited variables:
 * <ul>
 * <li>variance(EMAAccumulator)</li>
 * <li>period(int)</li>
 * <li>value(double)</li>
 * <li>numSamples(int)</li>
 * <li>lambda(double)</li>
 * <li>correction(double)</li>
 * </ul>
 * <p>
 * <b>History:</b><br>
 *  - [01.02.2008] Created (Mike Kroutikov)<br>
 *
 *  @author Mike Kroutikov
 */
public class EMAAR1Accumulator extends EMAVarAccumulator {
	/**
	 * private final double[] memory;<br/>
	 * circular buffer to hold lagged values (to compute covariance)
	 */
	private final double[] memory;
	/**
	 * private int start = 0;
	 */
	private int start = 0;
	/**
	 * sets the element at the current start(int) position from the associated memory(double[]) with the given val(double) and then increases the
	 * associated start(int) by one (reseting to 0 if it reaches the end)
	 * @param val
	 */
	private void pushValue(double val) {
		memory[start] = val;
		start = (start + 1) % memory.length;
	}
	/**
	 * returns the element at the ((start+memory.length)%memory.length) index from the associated memory(double[])
	 * @return
	 */
	private double getLaggedValue() {
		return memory[(start + memory.length) % memory.length];
	}
	/**
	 * private final EMAAccumulator covariance = new EMAAccumulator();
	 */
	private final EMAAccumulator covariance = new EMAAccumulator();
	/**
	 * constructs an EMAAR1Accumulator(EMAVarAccumulator subclass) using the given lag(int) to set the length of its associated memory(double[])
	 * @param lag
	 */
	public EMAAR1Accumulator(int lag) {
		if(lag < 1) throw new IllegalArgumentException("lag must be 1 or greater");
		memory = new double[lag];
	}
	/**
	 * <strong>1.</strong> sets the associated/inherited period(int) and lambda(double) with the given averagingPeriod(int) and 1-(1/averagingPeriod)<br/>
	 * <strong>2.</strong> sets the period(int) and lambda(double) of the associated variance(EMAAccumulator) with the given averagingPeriod(int) and 1-(1/averagingPeriod)<br/>
	 * <strong>3.</strong> sets the period(int) and lambda(double) of the associated covariance(EMAAccumulator) with the given averagingPeriod(int) and 1-(1/averagingPeriod)
	 */
	@Override
	public void setPeriod(int averagingPeriod) {
		super.setPeriod(averagingPeriod);
		covariance.setPeriod(averagingPeriod);
	}

	/**
	 * Lag that is used to compute covariance (and, therefore, Tau).
	 * Note that it is not recommended to use lags close to the value of Tau, because
	 * this will cause exponential amplifying of statistical noise (algorithm may become 
	 * unstable, returning abnormal values).<br/>
	 * returns the length of the associated memory(double[])
	 * @return lag value.
	 */
	public int getLag() {
		return memory.length;
	}
	
	/**
	 * Tau (correlation length). <br/>
	 * returns - getLag() / Math.log(1. - covariance.getMeanValue() / getVariance2());
	 * @return tau.
	 */
	public double getTau() {
		return - getLag() / Math.log(1. - covariance.getMeanValue() / getVariance2());
	}
	
	/**
	 * Phi (correlation factor).<br/>
	 * returns Math.exp(- 1. / getTau());
	 * @return phi.
	 */
	public double getPhi() {
		return Math.exp(- 1. / getTau());
	}

	/**
	 * Sigma (amplitude of noise).<br/>
	 * returns getVariance2() * (1. - phi * phi);
	 * @return sigma.
	 */
	public double getSigma() {
		double phi = getPhi();
		return getVariance2() * (1. - phi * phi);
	}

	/**
	 * Returns true if start-up is complete.<br/>
	 * returns whether the associated numSamples(int) is greater than the length of the associated memory(double[])
	 * @return true/false.
	 */
	public boolean isReady() {
		return getNumSamples() > getLag();
	}
	
	@Override
	public void accumulate(double value) {
		super.accumulate(value);
		
		if(getNumSamples() == 1) {
			// fill up memory
			for(int i = 0; i < memory.length; i++) pushValue(value);
		}

		double v = value - getLaggedValue();
		covariance.accumulate( 0.5 * v * v );

		pushValue(value);
	}
	
	public String toString() {
		return "EMAAR1Accumulator: period=" + getPeriod() + ", numSamples=" + getNumSamples()
		+ ", mu=" + getMeanValue() + ", phi=" + getPhi() + ", sigma=" + getSigma();
	}
}
